Reading Assembly
objdump
Starting from an executable file, we can read its Assembly code by disassembling it.
The standard tool for doing this is objdump
:
root@kali:~$ objdump -M intel -d <binary> | less
- Use
-M intel
for Intel syntax. The default syntax is AT&T. -d
stands for "disassembly".- pipe the output to
less
so you can navigate the Assembly code more easily.
Notice that every line contains an address, an opcode and an instruction. The opcode is simply the binary representation of that instruction.
Alternatively, you can use GDB and Ghidra that you learned about in the previous session.
GDB
The undisputed king of Assembly is by far the GNU DeBugger (GDB). It's just what its name says it is, but its beauty is in its versatility. GDB is a command-line debugger that allows us to print registers, variables, dump memory from any address, step through the code, go back through the call stack and much more. Today we will only get a glimpse of its power.
We are using the pwndbg
extension for GDB as it allows us to view the assembly code, stack (you'll learn about it in the next session) and registers.
Follow the instructions here to install it if you haven't done so already.
GDB can run Assembly instructions one by one and stops after each instruction. The current instruction is also clearly displayed. Below is a reduced list of useful GDB commands to get you going. Use it as a cheatsheet when you get stuck:
start
= start running the program frommain
list
= decompile and display C code Only works for executables compiled with-g
pdis
= disassemble and display instructions with nice syntax highlightingnext
/n
= run the current C code If it is a function call, it is executed without stepping into the function.nexti
/ni
= run the current Assembly instructionstep
/s
= if the debugger has reached a function call, step into it. Otherwise, it behaves likenext
/n
stepi
/si
= step into function (used for thecall
instruction in Assembly)break
/b <n>
= place a breakpoint at linen
break
/b *<address>
= place breakpoint at addresscontinue
/c
= run code until next breakpointinfo registers <name>
= display the values in all registers. If a name is specified, only the value in that register is displayedp <variable>
/<name>
= print the variable / number; similar toprintf
p/d = printf("%d")
p/c = printf("%c")
p/x = printf(“%x”)
p/u = printf(“%u”)
x <address>
= print data at the address (dereference it). By default, the output is represented in hexx/<n><d><f> <address>
-> printn
memory areas of sized
with formatf
:
n = any number; default = 1
d = b (byte - default) / h (half-word = short) / w (word = int)
f = (like p): x (hex - default) / c (char) / d (int, decimal) / u (unsigned) / s (string)
Examples:
x/20wx = 20 hex words (ints)
x/10hd = 10 decimal half-words (shorts)
x/10c = 10 ASCII characters
x/10b = 10 hex bytes (because x is the default)
set $<register> <value>
= sets the register to that value